package cn.qingyun.gis.modules.caronline.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.qingyun.gis.exception.CommonException;
import cn.qingyun.gis.modules.caronline.entity.CarInfo;
import cn.qingyun.gis.modules.caronline.entity.CarProofread;
import cn.qingyun.gis.modules.caronline.entity.dto.CarExportTotalDto;
import cn.qingyun.gis.modules.caronline.entity.dto.CarInfoTotalDto;
import cn.qingyun.gis.modules.caronline.mapper.CarInfoMapper;
import cn.qingyun.gis.modules.caronline.mapper.CarProofreadMapper;
import cn.qingyun.gis.modules.caronline.service.ICarAttachmentService;
import cn.qingyun.gis.modules.caronline.service.ICarInfoService;
import cn.qingyun.gis.modules.caronline.service.ICarProofreadService;
import cn.qingyun.gis.modules.express.config.Exception.CustomExceptionHandler;
import cn.qingyun.gis.modules.express.constant.ExpressStatus;
import cn.qingyun.gis.modules.express.constant.RedisContant;
import cn.qingyun.gis.modules.express.entity.ExpressRegion;
import cn.qingyun.gis.modules.express.entity.OperationLog;
import cn.qingyun.gis.modules.express.mapper.OperationLogMapper;
import cn.qingyun.gis.modules.express.request.ExpressProofreadRequest;
import cn.qingyun.gis.modules.express.request.ExpressQueryRequest;
import cn.qingyun.gis.modules.express.service.IExpressRegionService;
import cn.qingyun.gis.modules.express.util.SyncTool;
import cn.qingyun.gis.modules.init.system.constant.enums.RegionNumPermissionEnum;
import cn.qingyun.gis.modules.init.system.entity.SysLog;
import cn.qingyun.gis.modules.init.system.entity.SysUser;
import cn.qingyun.gis.modules.init.system.mapper.SysLogMapper;
import cn.qingyun.gis.modules.init.system.mapper.SysUserMapper;
import cn.qingyun.gis.modules.init.utils.JwtUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.io.WKTReader;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <p>
 * 网约车勘误 服务实现类
 * </p>
 *
 * @author guest
 * @since 2023-10-28
 */
@Service
@Slf4j
public class CarProofreadServiceImpl extends ServiceImpl<CarProofreadMapper, CarProofread> implements ICarProofreadService {
    @Resource
    private ICarProofreadService iCarProofreadService;
    @Resource
    private ICarInfoService iCarInfoService;
    @Resource
    private ICarAttachmentService iCarAttachmentService;

    @Resource
    private CarProofreadMapper carProofreadMapper;

    @Resource
    private CarInfoMapper carInfoMapper;

    @Resource
    private IExpressRegionService iExpressRegionService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private SysUserMapper sysUserMapper;
    @Resource
    private HttpServletResponse response;
    @Resource
    private SysLogMapper sysLogMapper;
    @Resource
    private CustomExceptionHandler customExceptionHandler;

    @Resource
    private OperationLogMapper operationLogMapper;

    @Override
    public void initCarExcel(MultipartFile file) {
        log.debug("开始初始化");
        new Thread(() -> {
            try {
                List<CarProofread> addDatas = Lists.newArrayList();
                EasyExcelFactory.read(file.getInputStream(), CarProofread.class, new ReadListener<CarProofread>() {
                    @Override
                    public void onException(Exception exception, AnalysisContext context) {
                        if (exception instanceof ExcelDataConvertException) {
                            log.error("解析异常, 行：{}", ((ExcelDataConvertException) exception).getRowIndex());
                        } else {
                            log.error("解析异常, {}", exception.getClass().getName());
                        }
                    }

                    @Override
                    public void invoke(CarProofread data, AnalysisContext context) {
                        data.setCreateTime(DateUtil.date());
                        data.setUpdateTime(DateUtil.date());
                        data.setProvince("山东");
                        data.setKwStatus(ExpressStatus.UN_PROOFED);
                        addDatas.add(data);

                        if (addDatas.size() == 100) {
                            iCarProofreadService.saveBatch(addDatas);
                            addDatas.clear();
                        }
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext context) {
                        iCarProofreadService.saveBatch(addDatas);
                        log.debug("导入完成");
                    }
                }).sheet().doReadSync();

            } catch (IOException e) {
                log.error("导入失败", e);
            }
        }).start();
    }

    @Override
    public void formatting() {
        iCarProofreadService.remove(new QueryWrapper<>());
        iCarInfoService.remove(new QueryWrapper<>());
        iCarAttachmentService.remove(new QueryWrapper<>());
    }

    @Override
    public void matchGrid() {

        AtomicInteger n = new AtomicInteger();

        // 获取微网格级别业务区划数据
        LambdaQueryWrapper<ExpressRegion> miroGridWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "5").ne(ExpressRegion::getGeometry, "");
        List<ExpressRegion> miroGridTotal = iExpressRegionService.list(miroGridWrapper);
        // 获取网格级别业务区划数据
        LambdaQueryWrapper<ExpressRegion> gridWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "4").ne(ExpressRegion::getGeometry, "");
        List<ExpressRegion> gridTotal = iExpressRegionService.list(gridWrapper);
        // 获取区县级别业务区划数据
        LambdaQueryWrapper<ExpressRegion> districtsWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "3").ne(ExpressRegion::getGeometry, "");
        List<ExpressRegion> districtsTotal = iExpressRegionService.list(districtsWrapper);
        // 获取地市级别业务区划数据
        LambdaQueryWrapper<ExpressRegion> cityWrapper = new QueryWrapper<ExpressRegion>().lambda().eq(ExpressRegion::getLevel, "2").ne(ExpressRegion::getGeometry, "");
        List<ExpressRegion> cityTotal = iExpressRegionService.list(cityWrapper);

        // 计算网约车未勘误信息数据条数
        int count = iCarProofreadService.count();
        int pages = (count / 1000) + 1;
        long start = System.currentTimeMillis();
        // 根据数据总量划分数据块并启动多线程
        SyncTool.doSync(pages, i -> {
            Page<CarProofread> page = new Page<>(i, 1000);
            List<CarProofread> proofreads = iCarProofreadService.page(page, null).getRecords();
            List<CarProofread> updateRecords = Lists.newArrayList();

            for (CarProofread data : proofreads) {
                log.debug(String.format("==================== 开始匹配网约车 %s====================", data.getId()));
                double lon = 0;
                double lat = 0;
                // 如果原始经纬度不为空
                if (ObjectUtil.isNotEmpty(data.getLon()) && ObjectUtil.isNotEmpty(data.getLat())) {
                    lon = data.getLon().doubleValue();
                    lat = data.getLat().doubleValue();
                }
                // 如果勘误经纬度不为空
                if (ObjectUtil.isNotEmpty(data.getKwLon()) && ObjectUtil.isNotEmpty(data.getKwLat())) {
                    lon = data.getKwLon().doubleValue();
                    lat = data.getKwLat().doubleValue();
                }
                try {
                    // 需要匹配经纬度都不为0
                    if ((lat != 0) && (lon != 0)) {
                        // 创建点对象
                        Point point = new GeometryFactory().createPoint(new Coordinate(lon, lat));
                        // 创建边界对象
                        Geometry geometry = null;
                        // 1.进行微网格匹配
                        for (ExpressRegion miroGridData : miroGridTotal) {

                            geometry = new WKTReader().read(miroGridData.getGeometry());
                            // 微网格匹配成功
                            if (geometry.contains(point)) {
                                data.setMicroGridId(miroGridData.getCode());
                                data.setMicroGridName(miroGridData.getName());
                                break;
                            }
                        }
                        // 2.进行网格匹配
                        for (ExpressRegion gridData : gridTotal) {
                            // 创建面对象
                            geometry = new WKTReader().read(gridData.getGeometry());
                            if (geometry.contains(point)) {
                                data.setGridId(gridData.getCode());
                                data.setGridName(gridData.getName());
                                break;
                            }
                        }
                        // 3.进行区县匹配
                        for (ExpressRegion disData : districtsTotal) {
                            // 创建面对象
                            geometry = new WKTReader().read(disData.getGeometry());
                            if (geometry.contains(point)) {
                                data.setRegionDistrictsCode(disData.getCode());
                                data.setRegionDistricts(disData.getName());
                                break;
                            }
                        }
                        // 4.进行地市匹配
                        for (ExpressRegion cityData : cityTotal) {
                            // 创建面对象
                            geometry = new WKTReader().read(cityData.getGeometry());
                            // 匹配地市
                            if (geometry.contains(point)) {
                                data.setRegionCityCode(cityData.getCode());
                                data.setRegionCity(cityData.getName());
                                break;
                            }
                        }
                    }
                    synchronized (this) {
                        n.incrementAndGet();
                        log.debug(String.format("=========== 当前匹配进度为%s / %s ===========", n, count));
                    }
                    updateRecords.add(data);
                } catch (Exception e) {
                    customExceptionHandler.handleException(String.format("网约车站点-%s,入格失败！|报错：%s", data.getPoiName(), e.getMessage()), start);
                }
            }
            // 批量导入匹配之后数据
            iCarProofreadService.updateBatchById(updateRecords);

        });

        log.debug(String.format("网约车数据匹配网格完成，====一共处理%s网约车数据====", count));
        SysLog sysLog = new SysLog();
        long finish = System.currentTimeMillis();
        sysLog.setLogContent("网约车数据匹配成功");
        sysLog.setCreateTime(DateUtil.date());
        sysLog.setLogType(3); //定时任务日志类型
        sysLog.setCreateBy("system");
        sysLog.setUserid("定时任务");
        sysLog.setCostTime(finish - start);
        sysLogMapper.insert(sysLog);
    }

    @Override
    public IPage<CarProofread> queryList(ExpressQueryRequest request) {
        //根据网格编码+工号+未勘误（勘误状态=0）+校对类型+修改时间降序匹配查询
        QueryWrapper<CarProofread> queryWrapper = new QueryWrapper<>();
        if (ObjectUtil.isNotEmpty(request.getLat()) || ObjectUtil.isNotEmpty(request.getLon())){
            queryWrapper.select("car_proofread.*," + String.format("ROUND(ST_Distance_Sphere(POINT(%s, %s), POINT(lon, lat))/1000,2) AS distance", request.getLon(), request.getLat()))
                    .orderByAsc("distance is null,distance");
        }
        queryWrapper.lambda().eq(CarProofread::getKwStatus, ExpressStatus.UN_PROOFED).orderByDesc(CarProofread::getUpdateTime);
        String[] gridIdStr = request.getGridId().split(",");
        List<String> gridIdList = Arrays.asList(gridIdStr);

        //创建分页对象
        IPage<CarProofread> page = new Page<>(request.getPageNum(), request.getPageSize());

        // 根据区域权限进行判定 以及 关键字模糊查询
        if (CharSequenceUtil.isNotEmpty(request.getGridType()) && Boolean.TRUE.equals(RegionNumPermissionEnum.pitchRegion(request.getGridType()))) {
            if (ObjectUtil.isNotEmpty(request.getKeyword())) {
                queryWrapper.lambda().and(
                        lwp -> lwp.like(CarProofread::getPoiAddress, request.getKeyword()).
                                or().
                                like(CarProofread::getPoiName, request.getKeyword())
                );
            }
            if (RegionNumPermissionEnum.PROVINCE.name().equals(request.getGridType()) && gridIdList.size() == 1) {
                return carProofreadMapper.selectPage(page, queryWrapper);
            } else {
                queryWrapper.lambda().and(
                        wp -> wp.in(CarProofread::getRegionCityCode, gridIdList).
                                or().
                                in(CarProofread::getRegionDistrictsCode, gridIdList).
                                or().
                                in(CarProofread::getGridId, gridIdList)
                );
            }
            return carProofreadMapper.selectPage(page, queryWrapper);
        }
        return new Page<>();
    }

    @Override
    @Transactional
    public String carProofread(ExpressProofreadRequest request) {
        RLock rlock = redissonClient.getLock(RedisContant.CAR_PROOF_LOCK);
        try {
            boolean lockRes = rlock.tryLock(10, TimeUnit.SECONDS);
            if (lockRes) {
                //1.处理express_proofread表
                CarProofread proofread = carProofreadMapper.selectById(request.getId());
                BeanUtil.copyProperties(request, proofread);
                proofread.setKwStatus(ExpressStatus.PROOFED); //更新勘误状态
                proofread.setUpdateTime(DateUtil.date());//更新修改(勘误)时间
                if (request.getGridBelongStatus() == 0) { // 优化：若勘误过程，站点不属于本网格，选择应属网格，勘误状态更新为：未勘误
                    proofread.setKwStatus(ExpressStatus.UN_PROOFED);
                    // 自动进行应归属网格匹配
                    proofread.setGridName(request.getBelongGrid());
                    proofread.setGridId(request.getBelongGridCode());
                    proofread.setRegionDistricts(request.getBelongCounty());
                    proofread.setRegionDistrictsCode(request.getBelongCountyCode());

                    // 添加日志
                    OperationLog operationLog = OperationLog.builder()
                            .logAccount(request.getKwPhone())
                            .operationId(request.getId())
                            .logType(0) // 0:应属网格操作
                            .operateType(2)  //1:外卖
                            .createTime(DateUtil.date()).build();
                    operationLogMapper.insert(operationLog);

                }
                carProofreadMapper.updateById(proofread);

                //2.处理car_info表
                CarInfo res = carInfoMapper.selectOne(new LambdaQueryWrapper<CarInfo>().eq(CarInfo::getCarProofreadId, request.getId()));
                CarInfo info = new CarInfo();
                if (ObjectUtil.isEmpty(res)) { //该外卖地址还未勘误
                    BeanUtil.copyProperties(request, info);
                    info.setId(null);
                    info.setCarProofreadId(request.getId());
                    info.setCreateTime(DateUtil.date()); // 初次勘误时间记录
                    info.setUpdateTime(DateUtil.date());
                    carInfoMapper.insert(info);
                } else {//外卖地址已勘误一次
                    BeanUtil.copyProperties(request, info);
                    info.setId(res.getId());
                    info.setCarProofreadId(request.getId());
                    info.setUpdateTime(DateUtil.date()); //勘误修改时间记录
                    carInfoMapper.updateById(info);
                }
                //3.图片上传
                iCarAttachmentService.upload(request.getPictureList(), request.getId());
                return "勘误成功";
            }
            return "系统繁忙，请稍后再试";

        } catch (Exception e) {
            throw new CommonException("勘误失败");
        } finally {
            rlock.unlock();
        }
    }

    @Override
    public IPage<CarInfoTotalDto> queryTotalList(ExpressQueryRequest request) {
        //创建分页对象
        IPage<CarInfoTotalDto> page = new Page<>(request.getPageNum(), request.getPageSize());

        List<String> gridEncodings = Arrays.asList(request.getGridId().split(","));
        String gridType = request.getGridType();

        // 根据区域权限进行判定
        if (CharSequenceUtil.isNotEmpty(gridType) && RegionNumPermissionEnum.pitchRegion(request.getGridType())) {
            return page.setRecords(carProofreadMapper.selectCarTotalList(page, gridEncodings, gridType, request.getKeyword(),request.getLon(),request.getLat()));
        }
        return new Page<>();
    }

    @SneakyThrows
    @Override
    public void exportCarTotal(HttpServletRequest request) {
        response.setContentType("application/vnd.ms-excel");
        String fileName = "网约车地址信息明细表";
        response.setHeader("content-disposition", String.format("attachment;filename=%s.xlsx", new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1) + DateUtil.now()));
        // 获取当前用户信息
        String currentUserName = JwtUtil.getUserNameByToken(request);
        // 根据用户信息查询区域编码
        SysUser user = sysUserMapper.selectOne(new QueryWrapper<SysUser>().lambda().
                select(SysUser::getRegionCode).
                eq(SysUser::getUsername, currentUserName));
        log.debug("当前登录用户信息：{} 以及相应配置地市权限编码{}", currentUserName, user.getRegionCode());
        String regionCode;
        if ("0000SD".equals(user.getRegionCode())) { // 当前省级配置工号
            regionCode = null;
        } else {
            regionCode = user.getRegionCode();
        }
        // 根据分页进行多线程并发导入
        int count = this.count(new QueryWrapper<>());
        try (ExcelWriter excelWriter = EasyExcelFactory.write(response.getOutputStream()).build()) {
            // 此处使用导入实体类充当导出
            WriteSheet writeSheet = EasyExcelFactory.writerSheet(0, "全量数据").head(CarExportTotalDto.class).build();
            int pages = (count / 1000) + 1;
            //并发导出
            SyncTool.doSync(pages, i -> {
                Page<CarExportTotalDto> page = new Page<>(i, 1000);
                IPage<CarExportTotalDto> infoTotalDtos = carProofreadMapper.selectCarTotal(page, null, regionCode);
                synchronized (excelWriter) {
                    excelWriter.write(infoTotalDtos.getRecords(), writeSheet);
                }
            });
        }
        log.debug("已成功导出{}条外卖地址全量数据", count);
    }

}
